/*******************************************************************************
 * This file is part of blueprint4j www.blueprint4j.org.
 * 
 *     blueprint4j is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     blueprint4j is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with blueprint4j. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package blueprint4j.db;

import java.util.Date;

import blueprint4j.utils.*;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.*;

public abstract class Field implements BindFieldInterface,java.io.Serializable {
    public static final int FIELD_UNIQUE=0x1;
    public static final int FIELD_KEY=0x2;
    public static final int FIELD_FOREIGN_KEY=0x4;
    public static final int FIELD_NOT_NULL=0x8;
    public static final int FIELD_NOT_REAL=0x10;
    public static final int FIELD_NAME=0x20;
    public static final int FIELD_READ_ONLY=0x40;
    public static final int FIELD_LOAD_ON_DEMAND=0x80;
    public static final int FIELD_DONT_LOAD=0x100;
    public static final int FIELD_WRITE_ONCE=0x200;
    public static final int FIELD_INVISABLE=0x400;
    
    private boolean changed = false;
    private String name;
    private String description;
    private int field_type = 0;
    protected Entity entity = null;
    private Object data = null,old_data = null;
    public final static int DATE_AS_CURRENT = 0;
    
    // VISUALATION. REQUIRED FIELDS TO BE ENTERED BEFORE THIS FIELD BECOMES VISIBLE
    private VectorBindFieldRequire fieldRequire = new VectorBindFieldRequire();
    private boolean visibleByDefault = true;
    
    /**
     * protected Field( String p_name, String p_description, boolean p_key_field
     * Name: Name of field -> equivalent to table.field_name
     * Description: Description of field
     * key_field: Is this field a key field in the table (index field)
     */
    protected Field( String p_name, int p_field_type ,String p_description,Entity p_entity ) {
        name = p_name;
        description = p_description;
        field_type = p_field_type;
        entity = p_entity;
        entity.addField(this);
        if (isUnique()) {
            new Index.UniqueIndex("idx_"+getName(),new Field[]{this},entity);
        }
    }
    
    public void addFieldRequirement(VectorBindFieldInterface field,Object value,boolean visible) {
        fieldRequire.add(new BindFieldRequire(field,value,visible));
    }
    
    public VectorBindFieldRequire getFieldRequirement() {return fieldRequire;}
    
    /**
     * If this field has a pre requirement field on it, then it would not be visible by default
     **/
    public void setVisibleByDefault(boolean visibleByDefault ) {
        this.visibleByDefault = visibleByDefault;
    }
    
    public boolean getVisibleByDefault() {
        return visibleByDefault;
    }
    
    public abstract Field newInstance(Entity entity);
    
    public boolean is(int filter) {
        return ((field_type & filter) == filter);
    }
    
    public boolean isNot(int filter) {
        return ((field_type & filter) != filter);
    }
    
    public boolean isFieldVirtual() {
        return ((field_type&FIELD_NOT_REAL)==FIELD_NOT_REAL);
    }
    
    public boolean isReadOnly() {
        if (
                ((field_type&FIELD_READ_ONLY)==FIELD_READ_ONLY) ||
                (((field_type&FIELD_WRITE_ONCE)==FIELD_WRITE_ONCE) && getObject() != null && getEntity().isLoaded())
                ) {
            return true;
        }
        return false;
    }
    
    public boolean isLoadOnDemand() {
        return((field_type&FIELD_LOAD_ON_DEMAND)==FIELD_LOAD_ON_DEMAND);
    }
    
    public boolean shouldLoad() {
        return(!((field_type&FIELD_DONT_LOAD)==FIELD_DONT_LOAD));
    }
    
    public void setBindable(Bindable bindable) {
        this.entity = (Entity) bindable;
    }
    
    public Entity getEntity() {
        return entity;
    }
    
    public int getFieldType() {return field_type;}
    
    public void setFieldType(int p_field_type) {
        field_type = p_field_type;
    }
    
        /*
         * returns if this field a Unique Field
         */
    boolean isUnique() {
        return ((field_type  & 0x1) == FIELD_UNIQUE);
    }
    
    /**
     * returns if this field a Key Field
     */
    public boolean isKey() {
        return ((field_type & 0x2) == FIELD_KEY);
    }
    
    public void setReadOnly(boolean readonly) {
        if (readonly) {
            field_type = (field_type | FIELD_READ_ONLY);
        } else {
            field_type = (field_type ^ FIELD_READ_ONLY);
        }
    }
    
    /**
     * returns if this field a Foreign Key Field
     */
    public boolean isForeignKey() {
        return ((field_type & 0x4) == FIELD_FOREIGN_KEY);
    }
    
    /**
     * May field be null
     */
    public boolean mayFieldBeNull() {
        return ((field_type & 0x8) != FIELD_NOT_NULL);
    }
    
    public boolean isNameField() {
        return ((field_type & 0x20) == FIELD_NAME);
    }
    
    public void setName(String name) {
        this.name= name;
    }
    
    /**
     * public String getName()
     *
     * Returns the name of this field
     */
    public String getName() {
        return name;
    }
    
    /**
     *
     */
    public String getDescription() {
        return description;
    }
    
    /**
     * public boolean isChanged()
     *
     * Returns changed status of field
     */
    public boolean isChanged() {
        return changed;
    }
    
    /**
     * protected void setChanged(boolean state)
     *
     * setChanged- changes changed status to [state]
     */
    public void setChanged( boolean state ) {
        changed = state;
    }
    
    public void markAsUnchanged() {
        setChanged(false);
    }
    
    /**
     * protected void setObject( Object p_data )
     *
     * updates data object for this field
     */
    public void setObject( Object p_data ) {
        // First set original data
        if ( (data == null && p_data != null) || (data != null && p_data == null) || (p_data != null && !p_data.equals( data ))) {
            old_data = data;
            data = p_data;
            setChanged( true );
        };
    }
    
    /**
     * protected Object getObject()
     *
     * gets the data object for this field
     */
    public Object getObject() {
        return data;
    }
    
    public Object getOldObject() {
        return old_data;
    }
    
    /**
     * public Object getSQLObject()
     *
     * gets the data object for this field as the database would expect it
     */
    public Object getSQLObject()
    throws DataException{
        return getObject();
    }
    
    /**
     * public boolean isNull()
     *
     * is this field value null
     */
    public boolean isNull() {
        return ( data == null );
    }
    
    protected void setValue( ResultSet result_set )
    throws DataException{
        setFieldValue(result_set);
        setChanged(false);
    }
    
    abstract protected void setFieldValue( ResultSet result_set ) throws DataException;
    
    abstract public String getSQLValue() throws DataException;
    
    abstract Object getPreparedStatementObject() throws DataException;
    
    abstract public String toString();
    
    public String getMemoryReference() {
        return super.toString();
    }
    
    public int getLength() {
        return 0;
    }
    
    public Bindable getBindable() {
        return entity;
    }
    
    public String getSerializable()
    throws BindException {
        return ""+getObject();
    }
    
    public void setSerializable(String p_value) throws BindException {
        setObject(p_value);
    }
    
    public String getValueAsString() {
        return toString();
    }
    
    public String getSQLType()
    throws DataException {
        return entity.getConnection().getDataBaseUtils().getSQLType(this);
    }
    
    /**
     * This will be run for all fields on the very first save on an entity
     * this should be used to perform setup
     */
    public void runOnFirstSave() throws DataException {
    }

    /**
     * Overwrite this to create a field type with specific validity requirements. Returns the issue with the field.
     * 
     * @return
     */
	public String isInvalid() {
		// TODO Auto-generated method stub
		return null;
	}
}
